﻿using BitShuva.Common;
using BitShuva.Models;
using BitShuva.Models.Indexes;
using Raven.Client;
using Raven.Client.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace BitShuva.Controllers
{
    [RoutePrefix("api/likes")]
    public class LikesController : UserContextController
    {
        [Route("random/{count}")]
        public async Task<IEnumerable<Song>> GetRandomLikedSongs(int count)
        {
            var user = await this.GetUserOrNull();
            var likedSongIds = await this.Session
                .Query<Like>()
                .Customize(x => x.RandomOrdering())
                .Customize(x => x.Include<Like>(l => l.SongId))
                .Where(l => l.Status == LikeStatus.Like && l.UserId == user.Id)
                .Select(l => l.SongId)
                .Take(count)
                .ToListAsync();

            var loadedSongs = await this.Session.LoadAsync<Song>(likedSongIds);
            return loadedSongs
                .Where(s => s != null)
                .Select(s => s.ToDto());
        }

        [HttpPost]
        [Route("like/{songId}")]
        public async Task<int> Like(string songId)
        {
            return await UpdateLikeStatus(songId, LikeStatus.Like);
        }

        [HttpPost]
        [Route("dislike/{songId}")]
        public async Task<int> Dislike(string songId)
        {
            return await UpdateLikeStatus(songId, LikeStatus.Dislike);
        }

        private async Task<int> UpdateLikeStatus(string songId, LikeStatus likeStatus)
        {
            var user = await GetUserOrNull();
            if (user != null)
            {
                var isReversal = false;
                var isNoChange = false;
                var existingLike = await this.Session
                    .Query<Like>()
                    .FirstOrDefaultAsync(l => l.SongId == songId && l.UserId == user.Id);
                if (existingLike != null)
                {
                    isReversal = existingLike.Status != likeStatus;
                    isNoChange = existingLike.Status == likeStatus;
                    existingLike.Status = likeStatus;
                    await this.Session.StoreAsync(existingLike);
                }
                else
                {
                    var newLikeStatus = new Like()
                    {
                        Status = likeStatus,
                        SongId = songId,
                        UserId = user.Id,
                        Date = DateTime.Now
                    };
                    await this.Session.StoreAsync(newLikeStatus);
                }

                // Update the community rank.
                var songInDb = await this.Session.LoadAsync<Song>(songId);
                if (songInDb != null)
                {
                    var multiplier = Match.Value(isReversal)
                        .With(_ => isReversal, 2)
                        .With(_ => isNoChange, 0)
                        .DefaultTo(1)
                        .Evaluate();
                    var changePositiveOrNegative = likeStatus == LikeStatus.Like ? 1 : -1;
                    songInDb.CommunityRank += multiplier * changePositiveOrNegative;
                    user.Preferences.Update(songInDb, likeStatus);

                    var communityRankStats = await this.Session
                        .Query<Song, Songs_AverageCommunityRank>()
                        .As<Songs_AverageCommunityRank.Results>()
                        .FirstOrDefaultAsync();
                    var averageSongRank = communityRankStats != null ? communityRankStats.RankAverage : 0;
                    var newStanding = Match.Value(songInDb.CommunityRank)
                        .With(v => v <= -5, CommunityRankStanding.VeryPoor)
                        .With(v => v <= -1, CommunityRankStanding.Poor)
                        .With(v => v <= averageSongRank * 2, CommunityRankStanding.Normal)
                        .With(v => v <= averageSongRank * 4, CommunityRankStanding.Good)
                        .With(v => v <= averageSongRank * 6, CommunityRankStanding.Great)
                        .DefaultTo(CommunityRankStanding.Best)
                        .Evaluate();
                    songInDb.CommunityRankStanding = newStanding;

                    return songInDb.CommunityRank;
                }
            }

            return 0;
        }
    }
}